package verins.algrtm;
import java.awt.*;
import java.io.*;
import java.awt.image.*;
import java.util.ArrayList;
import javax.imageio.ImageIO;

class Minucia{
    public int x;
    public int y;
    public int tipo;

    public Minucia(){

    }
}

class Huella
{
    public ArrayList<Minucia> minucias;

    private BufferedImage img;
    private int height;
    private int width;

    public static int[][][] mascaras=
    {
        {
            {1,0,0},
            {0,1,1},
            {1,0,0}
        },
        {
            {1,0,1},
            {0,1,0},
            {0,1,0}
        },
        {
            {0,0,1},
            {1,1,0},
            {0,0,1}
        },
        {
            {0,1,0},
            {0,1,0},
            {1,0,1}
        },
        {
            {0,1,0},
            {1,1,0},
            {0,0,1}
        },
        {
            {0,1,0},
            {0,1,1},
            {1,0,0}
        },
        {
            {1,0,0},
            {0,1,1},
            {0,1,0}
        },
        {
            {0,0,1},
            {1,1,0},
            {0,1,0}
        },
        {
            {0,0,0},
            {1,1,0},
            {0,0,0}
        },
        {
            {0,1,0},
            {0,1,0},
            {0,0,0}
        },
        {
            {0,0,0},
            {0,1,1},
            {0,0,0}
        },
        {
            {0,0,0},
            {0,1,0},
            {0,1,0}
        },
        {
            {1,0,0},
            {0,1,0},
            {0,0,0}
        },
        {
            {0,0,1},
            {0,1,0},
            {0,0,0}
        },
        {
            {0,0,0},
            {0,1,0},
            {0,0,1}
        },
        {
            {0,0,0},
            {0,1,0},
            {1,0,0}
        },
        {
            {0,1,0},
            {1,1,0},
            {0,1,0}
        },
        {
            {0,1,0},
            {1,1,1},
            {0,0,0}
        },
        {
            {0,1,1},
            {0,1,1},
            {0,1,0}
        },
        {
            {0,0,0},
            {1,1,1},
            {0,1,0}
        },
        {
            {1,0,1},
            {0,1,0},
            {1,0,0}
        },
        {
            {1,0,1},
            {0,1,0},
            {0,0,1}
        },
        {
            {0,0,1},
            {0,1,0},
            {1,0,1}
        },
        {
            {1,0,0},
            {0,1,0},
            {1,0,1}
        },
        {
            {0,1,0},
            {1,1,1},
            {0,1,0}
        },
        {
            {1,0,1},
            {0,1,0},
            {1,0,1}
        }
    };

    public static boolean compara(int[][] matriz, int i, int j, int[][] mascara){
        for (int m=0; m<3; m++)
            for (int n=0; n<3; n++)
                if (matriz[i+m-1][j+n-1]!=mascara[m][n]){
                    return false;
                }
        return true;
    }

    public static int MINUCIA_FIN;
    public static int MINUCIA_BIFURCACION;
    public static int obtenTipo(int tipo){
        if (tipo>=8 && tipo<=15) return MINUCIA_FIN;
        return MINUCIA_BIFURCACION;

    }

    public static void extraerMinucias(int[][] matriz, int height, int width, ArrayList<Minucia> minucias){
        Minucia minucia;
        for (int i=1; i<height-1; i++)
            for (int j=1; j<width-1; j++){
                for (int k=0; k<26; k++){
                    if (compara(matriz, i,j, mascaras[k])){
                        minucia=new Minucia();
                        minucia.x=i;
                        minucia.y=j;
                        minucia.tipo=obtenTipo(k);
                        minucias.add(minucia);
                    }
                }
            }
    }

    public static void imprimirHuella(int[][] matriz, int height, int width){
        for (int i = 0; i < height; i++){
                for (int j = 0; j< width; j++)
                    System.out.print((matriz[i][j]==0)?" ":"1");
                System.out.println();
        }
    }

    public static int cuentaPasos01(int[][] matriz, int i, int j){
        int conteo=0;
        if (matriz[i-1][j]==0 && matriz[i-1][j+1]==1) conteo++;
        if (matriz[i-1][j+1]==0 && matriz[i][j+1]==1) conteo++;
        if (matriz[i][j+1]==0 && matriz[i+1][j+1]==1) conteo++;
        if (matriz[i+1][j+1]==0 && matriz[i+1][j]==1) conteo++;
        if (matriz[i+1][j]==0 && matriz[i+1][j-1]==1) conteo++;
        if (matriz[i+1][j-1]==0 && matriz[i][j-1]==1) conteo++;
        if (matriz[i][j-1]==0 && matriz[i-1][j-1]==1) conteo++;
        if (matriz[i-1][j-1]==0 && matriz[i-1][j]==1) conteo++;
        return conteo;
    }

    public static int sumaAlrededores(int[][]matriz, int i, int j){
        return matriz[i-1][j]+matriz[i-1][j+1]+matriz[i][j+1]+matriz[i+1][j+1]+
               matriz[i+1][j]+matriz[i+1][j-1]+matriz[i][j-1]+matriz[i-1][j-1];
    }

    public static void borrarMarcados(int[][] matriz, boolean[][] aux, int height, int width){
        for (int i=0; i<height; i++)
            for (int j=0; j<width; j++)
                if (aux[i][j])
                    matriz[i][j]=0;
    }

    public static boolean iteracionA(int[][] matriz, int height, int width, boolean[][] aux){
        boolean seadelgazo=false;
        for (int i = 0; i < height; i++){
            for (int j = 0; j< width; j++){
                if (i==0 || j==0 || i==height-1 || j==width-1){
                    aux[i][j]=true;
                }
                else{
                    if (matriz[i][j]==0){
                        aux[i][j]=false;
                        continue;
                    }
                    int conteo=cuentaPasos01(matriz,i,j);
                    int suma=sumaAlrededores(matriz, i, j);
                    int producto135=matriz[i-1][j]*matriz[i][j+1]*matriz[i+1][j];
                    int producto357=matriz[i][j+1]*matriz[i+1][j]*matriz[i][j-1];
                    if ((suma>=2) && (suma<=6) && conteo==1 && producto135==0 && producto357==0){
                        aux[i][j]=true;
                        seadelgazo=true;
                    }
                    else
                        aux[i][j]=false;
                }
            }
        }
        if (seadelgazo)
           borrarMarcados(matriz, aux, height, width);

        return seadelgazo;
    }

    public static boolean iteracionB(int[][] matriz, int height, int width, boolean[][] aux){
        boolean seadelgazo=false;
        for (int i = 0; i < height; i++){
            for (int j = 0; j< width; j++){
                if (i==0 || j==0 || i==height-1 || j==width-1){
                    aux[i][j]=true;
                }
                else{
                    if (matriz[i][j]==0){
                        aux[i][j]=false;
                        continue;
                    }
                    int conteo=cuentaPasos01(matriz,i,j);
                    int suma=sumaAlrededores(matriz, i, j);
                    int producto137=matriz[i-1][j]*matriz[i][j+1]*matriz[i][j-1];
                    int producto157=matriz[i-1][j]*matriz[i+1][j]*matriz[i][j-1];
                    if ((suma>=2) && (suma<=6) && conteo==1 && producto137==0 && producto157==0){
                        aux[i][j]=true;
                        seadelgazo=true;
                    }
                    else
                        aux[i][j]=false;
                }
            }
        }
        if (seadelgazo)
            borrarMarcados(matriz, aux, height, width);
        return seadelgazo;
    }

    public static void adelagazarHuella(int[][] matriz, int height, int width){
        boolean seadelgazo=true;
        boolean aux[][] = new boolean[height][width];

        while (seadelgazo){
            seadelgazo=false;
            seadelgazo= iteracionA(matriz, height, width, aux) || seadelgazo;
            seadelgazo= iteracionB(matriz, height, width, aux) || seadelgazo;
        }
    }

    public Huella(byte[] bytea) throws IOException
    {
        try {
            //boolean exists = (new File(fichero)).exists();
            InputStream in = new ByteArrayInputStream(bytea);
            img = ImageIO.read(in);
            width = img.getWidth(null);
            height = img.getHeight(null);
            int[][] matriz = new int[height][width];
            int[] rgba = new int[width * height];
            PixelGrabber pgObj = new PixelGrabber(img, 0, 0, width, height, rgba, 0, width);
            pgObj.grabPixels();


            int suma = 0;
            for (int i = 0; i < height; i++)
                for (int j = 0; j< width; j++){
                    suma += rgba[i*width+j];
                    matriz[i][j] = (rgba[i*width+j]>=-250000 && rgba[i*width+j]<=-1)?0:1;
                }
            adelagazarHuella(matriz, height, width);

            minucias= new ArrayList<Minucia>();
            extraerMinucias(matriz, height, width, minucias);

            //imprimirHuella(matriz, height, width);



/*
            double media = suma / (width*height);

            double sumacuadrados = 0;
            double diferencia = 0;
            for (int i = 0; i < height; i++){
                for (int j = 0; j< width; j++){
                    diferencia = matriz[i][j]-media;
                    sumacuadrados += diferencia*diferencia;
                }
            }
            double varianzaCalculada = sumacuadrados/(height*width);
            double variacion;
            for (int i = 0; i < height; i++){
                for (int j = 0; j< width; j++){
                    diferencia = matriz[i][j]-media;
                    variacion = (int)Math.sqrt(VARIANZA_DESEADA*(diferencia*diferencia)/varianzaCalculada);
                    if (matriz[i][j]<media){
                        matriz[i][j]=(int)(media-variacion);
                    }
                    else{
                        matriz[i][j]=(int)(media+variacion);
                    }
                }
            }

            int heightLocal = height/HEIGHT_SEGMENTO;
            int widthLocal = width/WIDTH_SEGMENTO;
            int[][] esta_segmentada= new int[heightLocal][widthLocal];
            int[][] segmentada = new int[height*HEIGHT_SEGMENTO][widthLocal*WIDTH_SEGMENTO];
            for (int i = 0; i < heightLocal; i++){
                for (int j = 0; j < widthLocal; j++){
                    int sumaLocal = 0;
                    for (int m = i * HEIGHT_SEGMENTO; m < (i + 1) * HEIGHT_SEGMENTO; m++){
                        for (int n = j * WIDTH_SEGMENTO; n < (j + 1) * WIDTH_SEGMENTO; n++){
                            sumaLocal += matriz[m][n];
                        }
                    }
                    double mediaLocal = sumaLocal/(HEIGHT_SEGMENTO*WIDTH_SEGMENTO);

                    double sumaCuadradosLocal = 0;
                    double diferenciaLocal = 0;
                    for (int m = i * HEIGHT_SEGMENTO; m < (i + 1) * HEIGHT_SEGMENTO; m++){
                        for (int n = j * WIDTH_SEGMENTO; n < (j + 1) * WIDTH_SEGMENTO; n++){
                            diferenciaLocal = matriz[m][n] - mediaLocal;
                            sumaCuadradosLocal += diferenciaLocal * diferenciaLocal;
                        }
                    }
                    double varianzaCalculadaLocal = sumaCuadradosLocal/(HEIGHT_SEGMENTO*WIDTH_SEGMENTO);

                    esta_segmentada[i][j] = (varianzaCalculadaLocal<VARIANZA_DESEADA/2)? 0 : 1;
                    for (int k=0; k<HEIGHT_SEGMENTO; k++){
                        for (int l=0; l<WIDTH_SEGMENTO; l++){
                            segmentada[i*HEIGHT_SEGMENTO+k][j*WIDTH_SEGMENTO+l] =
                                esta_segmentada[i][j]==0?0:matriz[i*HEIGHT_SEGMENTO+k][j*WIDTH_SEGMENTO+l];
                        }
                    }
                }
            }

            int[][] Gx = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
            int[][] Gy = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};

            int[][] matrizGx = new int[height][width];
            int[][] matrizGy = new int[height][width];

            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++){
                    int sumaConvolucionGx = 0;
                    int sumaConvolucionGy = 0;
                    for (int m = 0; m < HEIGHT_MASCARA; m++){
                        for (int n = 0; n < WIDTH_MASCARA; n++){
                            int x = i + m - 1;
                            int y = j + n - 1;
                            sumaConvolucionGx += Gx[m][n]*((x<0 || x>=height || y<0 || y>=width)?0:segmentada[x][y]);
                            sumaConvolucionGy += Gy[m][n]*((x<0 || x>=height || y<0 || y>=width)?0:segmentada[x][y]);
                        }
                    }
                    matrizGx[i][j]=sumaConvolucionGx;
                    matrizGy[i][j]=sumaConvolucionGy;

                }


            double[][] Vx = new double[height][width];
            double[][] Vy = new double[height][width];
            double[][] angulo = new double[height][width];
            int sumaConvolucionVx = 0;
            int sumaConvolucionVy = 0;
            for (int i = 0; i < height; i++){
                for (int j = 0; j < width; j++){
                    sumaConvolucionVx = 0;
                    sumaConvolucionVy = 0;
                    for (int m = 0; m < HEIGHT_MASCARA; m++){
                        for (int n = 0; n < WIDTH_MASCARA; n++){
                            int x = i + m - 1;
                            int y = j + n - 1;
                            int GxLocal = (x<0 || x>=height || y<0 || y>=width)?0:matrizGx[x][y];
                            int GyLocal = (x<0 || x>=height || y<0 || y>=width)?0:matrizGy[x][y];
                            sumaConvolucionVx += 2*GxLocal*GyLocal;
                            sumaConvolucionVy += GxLocal*GxLocal*GyLocal*GyLocal;
                        }
                    }
                    Vx[i][j] = sumaConvolucionVx;
                    Vy[i][j] = sumaConvolucionVy;
                    angulo[i][j] = Math.atan(Vx[i][j]/Vy[i][j])/2;
                }
            }
            for (int i=0; i<heightLocal; i++){
                for (int j=0; j<widthLocal; j++){
                    //if (segmentada[i][j]==0){
                        for (int k=0; k<HEIGHT_SEGMENTO; k++){
                            for (int l=0; l<WIDTH_SEGMENTO; l++){
                                if ();
                            }
                        }

                    //}
                }
            }
*/
        } catch (IOException ex) {
            throw ex;
        } catch (InterruptedException ex) {
        }
    }

    public Huella(String fichero) throws IOException
    {
        try {
            //boolean exists = (new File(fichero)).exists();
            Toolkit toolkit = Toolkit.getDefaultToolkit();
            File file = new File(fichero);
            img = ImageIO.read(file);
            width = img.getWidth(null);
            height = img.getHeight(null);
            int[][] matriz = new int[height][width];
            int[] rgba = new int[width * height];
            PixelGrabber pgObj = new PixelGrabber(img, 0, 0, width, height, rgba, 0, width);
            pgObj.grabPixels();


            int suma = 0;
            for (int i = 0; i < height; i++)
                for (int j = 0; j< width; j++){
                    suma += rgba[i*width+j];
                    matriz[i][j] = (rgba[i*width+j]>=-250000 && rgba[i*width+j]<=-1)?0:1;
                }
            adelagazarHuella(matriz, height, width);

            minucias= new ArrayList<Minucia>();
            extraerMinucias(matriz, height, width, minucias);

            //imprimirHuella(matriz, height, width);



/*
            double media = suma / (width*height);

            double sumacuadrados = 0;
            double diferencia = 0;
            for (int i = 0; i < height; i++){
                for (int j = 0; j< width; j++){
                    diferencia = matriz[i][j]-media;
                    sumacuadrados += diferencia*diferencia;
                }
            }
            double varianzaCalculada = sumacuadrados/(height*width);
            double variacion;
            for (int i = 0; i < height; i++){
                for (int j = 0; j< width; j++){
                    diferencia = matriz[i][j]-media;
                    variacion = (int)Math.sqrt(VARIANZA_DESEADA*(diferencia*diferencia)/varianzaCalculada);
                    if (matriz[i][j]<media){
                        matriz[i][j]=(int)(media-variacion);
                    }
                    else{
                        matriz[i][j]=(int)(media+variacion);
                    }
                }
            }

            int heightLocal = height/HEIGHT_SEGMENTO;
            int widthLocal = width/WIDTH_SEGMENTO;
            int[][] esta_segmentada= new int[heightLocal][widthLocal];
            int[][] segmentada = new int[height*HEIGHT_SEGMENTO][widthLocal*WIDTH_SEGMENTO];
            for (int i = 0; i < heightLocal; i++){
                for (int j = 0; j < widthLocal; j++){
                    int sumaLocal = 0;
                    for (int m = i * HEIGHT_SEGMENTO; m < (i + 1) * HEIGHT_SEGMENTO; m++){
                        for (int n = j * WIDTH_SEGMENTO; n < (j + 1) * WIDTH_SEGMENTO; n++){
                            sumaLocal += matriz[m][n];
                        }
                    }
                    double mediaLocal = sumaLocal/(HEIGHT_SEGMENTO*WIDTH_SEGMENTO);

                    double sumaCuadradosLocal = 0;
                    double diferenciaLocal = 0;
                    for (int m = i * HEIGHT_SEGMENTO; m < (i + 1) * HEIGHT_SEGMENTO; m++){
                        for (int n = j * WIDTH_SEGMENTO; n < (j + 1) * WIDTH_SEGMENTO; n++){
                            diferenciaLocal = matriz[m][n] - mediaLocal;
                            sumaCuadradosLocal += diferenciaLocal * diferenciaLocal;
                        }
                    }
                    double varianzaCalculadaLocal = sumaCuadradosLocal/(HEIGHT_SEGMENTO*WIDTH_SEGMENTO);

                    esta_segmentada[i][j] = (varianzaCalculadaLocal<VARIANZA_DESEADA/2)? 0 : 1;
                    for (int k=0; k<HEIGHT_SEGMENTO; k++){
                        for (int l=0; l<WIDTH_SEGMENTO; l++){
                            segmentada[i*HEIGHT_SEGMENTO+k][j*WIDTH_SEGMENTO+l] =
                                esta_segmentada[i][j]==0?0:matriz[i*HEIGHT_SEGMENTO+k][j*WIDTH_SEGMENTO+l];
                        }
                    }
                }
            }

            int[][] Gx = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
            int[][] Gy = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};

            int[][] matrizGx = new int[height][width];
            int[][] matrizGy = new int[height][width];

            for (int i = 0; i < height; i++)
                for (int j = 0; j < width; j++){
                    int sumaConvolucionGx = 0;
                    int sumaConvolucionGy = 0;
                    for (int m = 0; m < HEIGHT_MASCARA; m++){
                        for (int n = 0; n < WIDTH_MASCARA; n++){
                            int x = i + m - 1;
                            int y = j + n - 1;
                            sumaConvolucionGx += Gx[m][n]*((x<0 || x>=height || y<0 || y>=width)?0:segmentada[x][y]);
                            sumaConvolucionGy += Gy[m][n]*((x<0 || x>=height || y<0 || y>=width)?0:segmentada[x][y]);
                        }
                    }
                    matrizGx[i][j]=sumaConvolucionGx;
                    matrizGy[i][j]=sumaConvolucionGy;

                }
            

            double[][] Vx = new double[height][width];
            double[][] Vy = new double[height][width];
            double[][] angulo = new double[height][width];
            int sumaConvolucionVx = 0;
            int sumaConvolucionVy = 0;
            for (int i = 0; i < height; i++){
                for (int j = 0; j < width; j++){
                    sumaConvolucionVx = 0;
                    sumaConvolucionVy = 0;
                    for (int m = 0; m < HEIGHT_MASCARA; m++){
                        for (int n = 0; n < WIDTH_MASCARA; n++){
                            int x = i + m - 1;
                            int y = j + n - 1;
                            int GxLocal = (x<0 || x>=height || y<0 || y>=width)?0:matrizGx[x][y];
                            int GyLocal = (x<0 || x>=height || y<0 || y>=width)?0:matrizGy[x][y];
                            sumaConvolucionVx += 2*GxLocal*GyLocal;
                            sumaConvolucionVy += GxLocal*GxLocal*GyLocal*GyLocal;
                        }
                    }
                    Vx[i][j] = sumaConvolucionVx;
                    Vy[i][j] = sumaConvolucionVy;
                    angulo[i][j] = Math.atan(Vx[i][j]/Vy[i][j])/2;
                }
            }
            for (int i=0; i<heightLocal; i++){
                for (int j=0; j<widthLocal; j++){
                    //if (segmentada[i][j]==0){
                        for (int k=0; k<HEIGHT_SEGMENTO; k++){
                            for (int l=0; l<WIDTH_SEGMENTO; l++){
                                if ();
                            }
                        }

                    //}
                }
            }
*/
        } catch (IOException ex) {
            throw ex;
        } catch (InterruptedException ex) {
        }
    }
}

public class AlgoritmoHuellasMinucias extends Algoritmo {

    public AlgoritmoHuellasMinucias(){
    }

    public AlgoritmoHuellasMinucias(String ruta1, String ruta2){
        this.ruta1 = ruta1;
        this.ruta2 = ruta2;
    }



    public int getVerificacion() throws IOException{
        Huella a = new Huella(ruta1);
        Huella b = new Huella(ruta2);
        int aciertos=0;
        for (int i=0; i<a.minucias.size(); i++)
            for (int j=0; j<b.minucias.size(); j++){
                int xa=a.minucias.get(i).x;
                int tipoa=a.minucias.get(i).tipo;
                int ya=a.minucias.get(i).y;
                int xb=b.minucias.get(j).x;
                int yb=b.minucias.get(j).y;
                int tipob=b.minucias.get(j).tipo;
                if (xb<=xa+10 && xb>=xa-10 && yb<=ya+10 && yb>=ya-10 && tipoa==tipob){
                    aciertos++;
                    break;
                }
            }
        return aciertos*100/a.minucias.size();
    }

    public int getVerificacion(byte[] bytea, byte[] byteb) throws IOException{
        Huella a = new Huella(bytea);
        Huella b = new Huella(byteb);
        int aciertos=0;
        for (int i=0; i<a.minucias.size(); i++)
            for (int j=0; j<b.minucias.size(); j++){
                int xa=a.minucias.get(i).x;
                int tipoa=a.minucias.get(i).tipo;
                int ya=a.minucias.get(i).y;
                int xb=b.minucias.get(j).x;
                int yb=b.minucias.get(j).y;
                int tipob=b.minucias.get(j).tipo;
                if (xb<=xa+10 && xb>=xa-10 && yb<=ya+10 && yb>=ya-10 && tipoa==tipob){
                    aciertos++;
                    break;
                }
            }
        return aciertos*100/a.minucias.size();
    }
}
